package model;
import view.SpelOmgeving;
import java.util.ArrayList;

/**
 * De klasse  is de achtergrond van de visuele map. Hier worden
 * alle berekeningen uitgevoerd en worden de gegevens van objecten bijgehouden.
 * @author Alvin & Roy
 */
public class GameMap {
    /** De speler */
    private Speler speler;
    /** Vijanden */
    private ArrayList<Vijand> vijanden;
    /** Alle velden */
    private Veld[][] velden;
    /** De spel omgeving uit de view package */
    private static SpelOmgeving omgeving;

    /**
     * Constructor van GameMap
     * @param verticaleVelden Het aantal verticale velden
     * @param horizontaleVelden Het aantal horizontale velden
     * @param beweegbareObstakels Het aantal beweegbare obstakels
     * @param onbeweegbareObstakels Het aantal onbeweegbare obstakels
     * @param o De spel omgeving, verantwoordelijk voor het tekenen van de 
     * spelwereld
     */
    public GameMap(int verticaleVelden, int horizontaleVelden,
            int beweegbareObstakels, int onbeweegbareObstakels,
            SpelOmgeving o){

        //set SpelOmgeving
        omgeving = o;

        //Maak de velden en de buren aan
        velden = new Veld[horizontaleVelden][verticaleVelden];
        createVelden(verticaleVelden, horizontaleVelden);
        //controleer of de wereld rond is
        if(Opties.getRanden()){
            setBuren();
        }else{
            setBurenGeenRanden();
        }

        //Maak de speler aan en plaats deze.
        //Standaard geldt x = 3 & y = 3
        speler = new Speler(velden[3][3]);
        velden[3][3].setSpelObject(speler);

        //maak de vijanden arraylist
        vijanden = new ArrayList<Vijand>();

        //maak de vijanden aan en plaats deze
        this.createVijanden();
        
        //maak beweegbare obstakels aan en plaats deze
        createObstakels(beweegbareObstakels, true);

        //maak onbeweegbare obstakels aan en plaats deze
        createObstakels(onbeweegbareObstakels, false);

        //maak de noten aan en plaats deze
        createNoten(Opties.getAantalNootjes());
    }

    /**
     * Constructor zonder spelobjecten, vijanden en speler
     * --Deze functie bestaat voor de unit test, daarom is de constructor niet volledig--
     * @param verticaleVelden Aantal verticale velden
     * @param horizontaleVelden Aantal horizontale velden
     * @param o De spelomgeving
     */
    public GameMap(int verticaleVelden, int horizontaleVelden, SpelOmgeving o){

        omgeving = o;

        //Maak de velden en de buren aan
        velden = new Veld[horizontaleVelden][verticaleVelden];
        createVelden(verticaleVelden, horizontaleVelden);
        setBuren();

    }

    /**
     * Maak de velden aan
     * @param vertVelden Het aantal verticale velden
     * @param horiVelden Het aantal horizontale velden
     */
    public void createVelden(int vertVelden, int horiVelden){
        for(int i = 0; i<horiVelden;i++){
            for(int j = 0; j<vertVelden;j++){
                velden[i][j] = new Veld(i,j);
            }
        }
    }

    /**
     * Maak de vijanden aan
     */
    private void createVijanden(){
        //voor het aantal vijanden dat ingesteld is
        for(int i = 0; i<Opties.getAantalVijanden(); i++){
            boolean vijandGeplaatst = false;
            //zolang de vijanbd nog niet geplaatst is
            while(!vijandGeplaatst){
                //kies een willekeurig veld
                Veld v = velden[(int)(Math.random()*velden.length)][(int)(Math.random()*velden[0].length)];
                //controleer of er al een vijand staat
                if(v.getSpelObject() == null){
                    //zo niet maak de vijand aan
                    Vijand vijand = new Vijand(v,speler, new PathFinder(this, velden.length*velden[0].length, false));
                    v.setSpelObject(vijand);
                    vijandGeplaatst = true;
                    vijanden.add(vijand);
                }
            }
        }
    }
    /**
     * Maak de obstakels aan
     * @param aantal Het aantal obstakels aan te maken
     * @param movable Beweegbaar of niet
     */
    private void createObstakels(int aantal, boolean movable){
        for(int i = 0; i<aantal; i++){
            boolean obstakelGeplaatst = false;
            //zolang het obstakel nog niet geplaatst is
            while(!obstakelGeplaatst){
                //kies een willekeurig veld
                Veld v = velden[(int)(Math.random()*velden.length)][(int)(Math.random()*velden[0].length)];
                //controleer of er al een object staat
                if(v.getSpelObject() == null){
                    //zo niet maak het obstakel aan
                    Obstakel o = new Obstakel(v,movable);
                    v.setSpelObject(o);
                    obstakelGeplaatst = true;
                }
            }
        }
    }

    /**
     * Maak de noten aan
     * @param aantal Het aantal noten
     */
    private void createNoten(int aantal){
        //voor het aantal noten
        for(int i = 0; i<aantal; i++){
            boolean nootGeplaatst = false;
            //zolang de noot nog niet geplaatst is
            while(!nootGeplaatst){
                //kies een willekeurig veld
                Veld v = velden[(int)(Math.random()*velden.length)][(int)(Math.random()*velden[0].length)];
                //controleer of er al een object staat
                if(v.getSpelObject() == null){
                    //zo niet maak de noot aan
                    Noot n = new Noot(v);
                    v.setSpelObject(n);
                    nootGeplaatst = true;
                }
            }
        }
    }

    /**
     * Geef de buren van de velden aan.
     * Alleen de buren in de NOordelijke en Westerlijke Richting worden
     * aangeroepen, tijdens die aanroep wordt de wederbuur (zuid en oost)
     * ook aangegeven. Dit is efficienter dan alle 4 de buren aangeven, waarbij
     * dubbele aanroepen ontstaan
     */
    public void setBuren(){
        for(int i = 0; i<velden.length;i++){
            for(int j = 0; j<velden[0].length;j++){
                //Als het veld niet aan de rand zit
                if(j != 0){
                    //Geef de Noord en Zuid buur aan
                    velden[i][j].addBuur(Richting.N, velden[i][j-1]);
                    velden[i][j-1].addBuur(Richting.Z, velden[i][j]);
                }
                //Als het veld niet aan de rand zit
                if(i != 0){
                    //Geef de West en Oost buur aan
                    velden[i][j].addBuur(Richting.W, velden[i-1][j]);
                    velden[i-1][j].addBuur(Richting.O, velden[i][j]);
                }

            }
        }
    }

    /**
     * Geef de buren van de velden aan.
     * Alleen de buren in de NOordelijke en Westerlijke Richting worden
     * aangeroepen, tijdens die aanroep wordt de wederbuur (zuid en oost)
     * ook aangegeven. Dit is efficienter dan alle 4 de buren aangeven, waarbij
     * dubbele aanroepen ontstaan
     *
     * Vervolgens worden voor alle velden aan de randen de buren aangegeven.
     */
    public void setBurenGeenRanden(){
        for(int i = 0; i<velden.length;i++){
            for(int j = 0; j<velden[0].length;j++){
                //Als het veld niet aan de rand zit
                if(j != 0){
                    //Geef de Noord en Zuid buur aan
                    velden[i][j].addBuur(Richting.N, velden[i][j-1]);
                    velden[i][j-1].addBuur(Richting.Z, velden[i][j]);
                }else{
                    velden[i][j].addBuur(Richting.N, velden[i][velden[0].length-1]);
                    velden[i][velden[0].length-1].addBuur(Richting.Z, velden[i][j]);
                }
                //Als het veld niet aan de rand zit
                if(i != 0){
                    //Geef de West en Oost buur aan
                    velden[i][j].addBuur(Richting.W, velden[i-1][j]);
                    velden[i-1][j].addBuur(Richting.O, velden[i][j]);
                }else{
                    velden[i][j].addBuur(Richting.W, velden[velden.length-1][j]);
                    velden[velden.length-1][j].addBuur(Richting.O, velden[i][j]);
                }

            }
        }
    }

    /**
     * Haal de speler op
     * @return De speler
     */
    public Speler getSpeler(){
        return speler;
    }

    /**
     * Haal de vijanden op
     * @return De vijanden
     */
    public ArrayList<Vijand> getVijanden(){
        return vijanden;
    }

    /**
     * Haal alle velden op
     * @return Het twee-dimensionale array met velden
     */
    public Veld[][] getVelden(){
        return velden;
    }

    /**
     * Haal een veld op
     * @param x De x locatie van het veld
     * @param y De y locatie van het veld
     * @return Het veld
     */
    public Veld getVeld(int x, int y){
        return velden[x][y];
    }

    /**
     * Roep de repaint functie van de spel omgeving aan.
     */
    public static void teken(){
        //kan null zijn voor JUnit tests
        if(omgeving != null){
            omgeving.repaint();
        }
    }

    /**
     * Onveilig, allen voor testen, NIET GEBRUIKEN
     * @param movable is het beweegbaar
     * @param x x positie
     * @param y y positie
     */
    public void addObstakel(boolean movable, int x, int y){
        velden[x][y].setSpelObject(new Obstakel(velden[x][y], movable));
    }

    /**
     * Onveilig, allen voor testen, NIET GEBRUIKEN
     * @param x x positie
     * @param y y positie
     */
    
     public void addNoot(int x, int y){
        velden[x][y].setSpelObject(new Noot(velden[x][y]));
    }
}
